package rexsee.core.transportation;

import java.io.DataOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;

import rexsee.core.application.RexseeApplication;
import rexsee.core.browser.Browser;
import rexsee.core.browser.clazz.JavascriptInterface;
import rexsee.core.lang.RexseeLanguage;
import rexsee.core.utilities.Escape;
import android.net.Uri;
import android.webkit.CookieManager;

public class RexseeAjax implements JavascriptInterface {

	public static final String INTERFACE_NAME = "Ajax";
	@Override
	public String getInterfaceName() {
		return mBrowser.application.resources.prefix + INTERFACE_NAME;
	}
	@Override
	public JavascriptInterface getInheritInterface(Browser childBrowser) {
		return new RexseeAjax(childBrowser);
	}
	@Override
	public JavascriptInterface getNewInterface(Browser childBrowser) {
		return new RexseeAjax(childBrowser);
	}

	public static final String EVENT_ONAJAXPROGRESSCHANGED = "onAjaxProgressChanged";
	public static final String EVENT_ONAJAXRETURNED = "onAjaxReturned";
	public static final String EVENT_ONAJAXTIMEOUT = "onAjaxTimeout";

	public static final int AJAX_PROGRESS_DONE = 1;
	public static final int AJAX_PROGRESS_ONGOING = 0;
	public static final int AJAX_PROGRESS_CONNECTING = -1;
	public static final int AJAX_PROGRESS_TIMEOUT = -2;
	public static final int AJAX_PROGRESS_EXCEPTION = -3;

	public static abstract class AjaxProgressListener {
		public abstract void run(String ajaxUrl, int status, int totalSize, int downloadedSize, String ajaxResponse, Runnable stopTheAjax);
	}

	private final Browser mBrowser;
	private String syncAjaxRtn = null;

	public RexseeAjax(Browser browser) {

		mBrowser = browser;
		mBrowser.eventList.add(EVENT_ONAJAXPROGRESSCHANGED);
		mBrowser.eventList.add(EVENT_ONAJAXRETURNED);
		mBrowser.eventList.add(EVENT_ONAJAXTIMEOUT);

	}

	public void submit(String url, final String postData, final AjaxProgressListener progressListener, final String encode, final boolean callbackJs) {
		final String ajaxUrl = mBrowser.urlListeners.getAbsoluteUrl(url);
		Uri uri = Uri.parse(ajaxUrl);
		if (uri == null) {
			if (progressListener != null) progressListener.run(ajaxUrl, AJAX_PROGRESS_EXCEPTION, 0, 0, "Invalid url.", null);
			mBrowser.exception(getInterfaceName(), url + ":Invalid url.");
			return;
		}
		String scheme = uri.getScheme();
		if (scheme == null || (!scheme.equals("http") && !scheme.equals("https"))) {
			if (progressListener != null) progressListener.run(ajaxUrl, AJAX_PROGRESS_EXCEPTION, 0, 0, "Invalid url.", null);
			mBrowser.exception(getInterfaceName(), url + ":Invalid url.");
			return;
		}
		if (!mBrowser.domainWhiteList.isValidUrl(ajaxUrl)) {
			if (progressListener != null) progressListener.run(ajaxUrl, AJAX_PROGRESS_EXCEPTION, 0, 0, "Url not allowed.", null);
			mBrowser.exception(getInterfaceName(), url + ":Url not allowed.");
			return;
		}
		new Thread() {
			private boolean stop = false;
			private final Runnable stopRunnable = new Runnable() {
				@Override
				public void run() {
					stop = true;
					if (progressListener != null) progressListener.run(ajaxUrl, AJAX_PROGRESS_EXCEPTION, 0, 0, "User canceled.", null);
				}
			};
			@Override
			public void run() {
				int timeout = mBrowser.application.getTimeout() * 1000;
				try {
					byte[] postBytes = new String(postData.getBytes(), encode).getBytes(encode);
					if (progressListener != null) progressListener.run(ajaxUrl, AJAX_PROGRESS_CONNECTING, 0, 0, null, stopRunnable);
					URL url = new URL(ajaxUrl);
					HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
					urlConnection.setRequestMethod("POST");
					urlConnection.setDoInput(true);
					urlConnection.setDoOutput(true);
					urlConnection.setConnectTimeout(timeout);
					urlConnection.setReadTimeout(timeout);
					urlConnection.setUseCaches(false);
					CookieManager cookieManager = CookieManager.getInstance();
					String cookie = cookieManager.getCookie(ajaxUrl);
					if (cookie != null) {
						urlConnection.setRequestProperty("Cookie", cookie);
						//After urlConnection.connect(); could retrieve sessionId with urlConnection.getHeaderField("Set-Cookie");
					}
					urlConnection.setRequestProperty("User-Agent", mBrowser.getSettings().getUserAgentString());
					urlConnection.setRequestProperty("Connection", "close");
					//urlConnection.setRequestProperty("Connection", "Keep-Alive");
					//urlConnection.setRequestProperty("Accept-Language", "zh-CH");
					urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
					urlConnection.setRequestProperty("Content-Length", "" + postBytes.length);
					urlConnection.connect();
					DataOutputStream dos = new DataOutputStream(urlConnection.getOutputStream());
					dos.write(postBytes);
					dos.flush();
					dos.close();
					if (cookieManager.acceptCookie()) {
						////Set session cookie
						String cookieVal = null;
						String cookieKey = null;
						String sessionCookie = "";
						for (int i = 1; (cookieKey = urlConnection.getHeaderFieldKey(i)) != null; i++) {
							if (cookieKey.equalsIgnoreCase("set-cookie")) {
								cookieVal = urlConnection.getHeaderField(i);
								cookieVal = cookieVal.substring(0, cookieVal.indexOf(";"));
								sessionCookie += cookieVal + ";";
							} else {
							}
						}
						if (sessionCookie != "") cookieManager.setCookie(ajaxUrl, cookieVal);
					}
					InputStream inputStream = urlConnection.getInputStream();
					int totalSize = urlConnection.getContentLength();
					int downloadedSize = 0;
					byte[] buffer = new byte[1024];
					int bufferLength = 0;
					String rtn = "";
					while ((bufferLength = inputStream.read(buffer)) > 0 && !stop) {
						rtn += new String(buffer, 0, bufferLength, encode);
						downloadedSize += bufferLength;
						if (progressListener != null) progressListener.run(ajaxUrl, AJAX_PROGRESS_ONGOING, totalSize, downloadedSize, rtn, stopRunnable);
					}
					if (!stop) {
						if (progressListener != null) progressListener.run(ajaxUrl, AJAX_PROGRESS_DONE, totalSize, downloadedSize, rtn, null);
						if (callbackJs) mBrowser.eventList.run(EVENT_ONAJAXRETURNED, new String[]{ajaxUrl, Escape.escape(rtn)});
					}
				} catch (SocketTimeoutException ste) {
					if (!stop) {
						if (progressListener != null) progressListener.run(ajaxUrl, AJAX_PROGRESS_TIMEOUT, 0, 0, String.valueOf(timeout), null);
						if (callbackJs) mBrowser.eventList.run(EVENT_ONAJAXTIMEOUT, new String[]{ajaxUrl, String.valueOf(timeout)});
					}
				} catch (Exception e) {
					if (!stop) {
						if (progressListener != null) progressListener.run(ajaxUrl, AJAX_PROGRESS_EXCEPTION, 0, 0, e.getLocalizedMessage(), null);
						mBrowser.exception(getInterfaceName(), ajaxUrl + ":" + e.getLocalizedMessage());
					}
				}
			}
		}.start();
	}

	//JavaScript Interface
	public void sendMessage(final String ajaxUrl, final String postData) {
		submit(ajaxUrl, postData, null, "UTF-8", false);
	}
	public void submit(String ajaxUrl, String postData, final String encode) {
		submit(ajaxUrl, postData, encode, true);
	}
	public void submit(String ajaxUrl, String postData, final String encode, final boolean cancelable) {
		submit(ajaxUrl, postData, new AjaxProgressListener() {
			@Override
			public void run(String ajaxUrl, int status, int totalSize, int downloadedSize, String ajaxResponse, Runnable stopTheAjax) {
				if (status == AJAX_PROGRESS_CONNECTING) {
					if (cancelable) {
						mBrowser.progressDialog.showWithRunnable(RexseeLanguage.PROGRESS_CONNECT, stopTheAjax);
					} else {
						mBrowser.progressDialog.show(RexseeLanguage.PROGRESS_CONNECT);
					}
				} else if (status == AJAX_PROGRESS_TIMEOUT) {
					mBrowser.progressDialog.hide();
				} else if (status == AJAX_PROGRESS_EXCEPTION) {
					mBrowser.progressDialog.hide();
				} else if (status == AJAX_PROGRESS_ONGOING) {
					String progress = (totalSize < 0) ? "" : (downloadedSize * 100 / totalSize) + "%";
					if (cancelable) {
						mBrowser.progressDialog.showWithRunnable(RexseeLanguage.PROGRESS_ONGOING + "......" + progress + " ", stopTheAjax);
					} else {
						mBrowser.progressDialog.show(RexseeLanguage.PROGRESS_ONGOING + "......" + progress + " ");
					}
				} else if (status == AJAX_PROGRESS_DONE) {
					mBrowser.progressDialog.hide();
				}
			}
		}, encode, true);
	}
	public void submitWithCallback(String ajaxUrl, String postData, final String encode) {
		submit(ajaxUrl, postData, new AjaxProgressListener() {
			@Override
			public void run(String ajaxUrl, int status, int totalSize, int downloadedSize, String ajaxResponse, Runnable stopTheAjax) {
				mBrowser.eventList.run(EVENT_ONAJAXPROGRESSCHANGED, new String[]{ajaxUrl, String.valueOf(totalSize), String.valueOf(downloadedSize)});
			}
		}, encode, true);
	}
	public String syncSubmit(String url, final String postData, final String encode) {
		return syncSubmit(url, postData, encode, true, true);
	}
	public String syncSubmit(String url, final String postData, final String encode, final boolean showProgress, final boolean cancelable) {
		syncAjaxRtn = null;
		submit(url, postData, new AjaxProgressListener() {
			@Override
			public void run(String ajaxUrl, int status, int totalSize, int downloadedSize, String ajaxResponse, final Runnable stopTheAjax) {
				if (status == RexseeAjax.AJAX_PROGRESS_CONNECTING) {
					if (cancelable) {
						if (showProgress) mBrowser.progressDialog.showWithRunnable(RexseeLanguage.PROGRESS_CONNECT, stopTheAjax);
					} else {
						if (showProgress) mBrowser.progressDialog.show(RexseeLanguage.PROGRESS_CONNECT);
					}
				} else if (status == RexseeAjax.AJAX_PROGRESS_TIMEOUT) {
					syncAjaxRtn = RexseeApplication.EXCEPTION_PREFIX + ajaxResponse;
				} else if (status == RexseeAjax.AJAX_PROGRESS_EXCEPTION) {
					syncAjaxRtn = RexseeApplication.EXCEPTION_PREFIX + ajaxResponse;
				} else if (status == RexseeAjax.AJAX_PROGRESS_ONGOING) {
					int p = (totalSize < 0) ? -1 : (downloadedSize * 100 / totalSize);
					String progress = (p <= 0 || p > 100) ? "" : String.valueOf(p) + "%";
					if (cancelable) {
						if (showProgress) mBrowser.progressDialog.showWithRunnable(RexseeLanguage.PROGRESS_ONGOING + "......" + progress + " ", stopTheAjax);
					} else {
						if (showProgress) mBrowser.progressDialog.show(RexseeLanguage.PROGRESS_ONGOING + "......" + progress + " ");
					}
				} else if (status == RexseeAjax.AJAX_PROGRESS_DONE) {
					syncAjaxRtn = ajaxResponse;
				}
			}
		}, encode, false);
		while (syncAjaxRtn == null) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
			}
		}
		if (showProgress) mBrowser.progressDialog.hide();
		return syncAjaxRtn;
	}

}
